package top.jle.jpa.basic.dao;


import jakarta.persistence.criteria.*;
import org.apache.commons.lang3.Validate;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.sqm.tree.domain.AbstractSqmQualifiedJoin;
import org.hibernate.query.sqm.tree.domain.SqmEntityValuedSimplePath;
import org.hibernate.query.sqm.tree.domain.SqmPath;
import org.hibernate.query.sqm.tree.domain.SqmSingularJoin;
import top.jle.jpa.basic.exception.BasicCustomException;
import top.jle.jpa.basic.exception.BasicExceptionCode;

import java.io.Serial;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import java.util.Set;


/**
 * 属性过滤条件封装类, 主要记录页面中简单的搜索过滤条件.
 * 建议通过PreFilters组装查询条件,不直接调用构造函数
 * @author jim
 */
public final class PropertyFilter implements PreFilter {

	/**
	 *
	 */
	@Serial
	private static final long serialVersionUID = -3135699435409378892L;

	public static final String FILTER_PREFIX ="PF";

	/**
	 * 多个属性间OR关系的分隔符. 例如:LIKES_name_OR_loginName
	 *
	 */
	public static final String OR_SEPARATOR = "_OR_";

	/*
	  属性比较类型 N开头表示not
	  C开头表示大小写敏感(case-insensitive)，默认的比较或者like对于字符串都是忽略大小写的

	 */
	/**
	 * @author admin
	 *
	 */
	public enum MatchType {
		/**
		 * 等于
		 */
		EQ,
		/**
		 * like '%value%'
		 */
		LIKE,
		/**
		 * 小于
		 */
		LESS_THAN,
		/**
		 * 大于
		 */
		GREATER_THAN,
		/**
		 * 小于或等于
		 */
		LESS_THAN_OR_EQ,
		/**
		 * 大于或等于
		 */
		GREATER_THAN_OR_EQ,
		/**
		 * in
		 */
		IN(1,20000),
		/**
		 * not in
		 */
		NOT_IN(1,20000),
		/**
		 * 不等于(忽略大小写)
		 */
		NOT_EQ,
		/**
		 * not like '%value%'(忽略大小写)
		 */
		NOT_LIKE,
		/**
		 * start with value
		 */
		START_WITH,
		END_WITH,
		IS_NULL(0,0),
		IS_NOT_NULL(0,0),
		BETWEEN(2,2),
		ARRAY_CONTAINS(1,1)
		;
		public final int valueMinLength;
		public final int valueMaxLength;

		MatchType(){
			this.valueMinLength =1;
			this.valueMaxLength =1;
		}
		MatchType(int min,int max){
			this.valueMinLength =min;
			this.valueMaxLength =max;
		}
	}

	/** 属性数据类型. */
	public enum PropertyType {
		S(String.class), I(Integer.class), L(Long.class), N(Double.class), D(Date.class), B(Boolean.class);

		private final Class<?> clazz;

		PropertyType(Class<?> clazz) {
			this.clazz = clazz;
		}

		public Class<?> getValue() {
			return clazz;
		}
	}

	private final MatchType matchType;
	private final transient Object[] matchValues;

	private Class<?> propertyClass;
	private final String propertyName;


	/**
	 * values为具体类型值的构造函数
	 *
	 * @param propertyNames
	 *            属性名
	 * @param matchType
	 *            匹配类型 {@link MatchType}
	 * @param matchValues
	 *            值数组，MatchType为BETWEEN类型时，长度必须是2，其他为1，值必须是具体类型的值
	 */
	public PropertyFilter(final String propertyNames, MatchType matchType, Object... matchValues) {
		validateMatchValuesLength(propertyNames,matchType,matchValues);
		this.propertyName = propertyNames;
		this.matchType = matchType;
		this.matchValues = matchValues;

	}


	private void validateMatchValuesLength(final String propertyName, MatchType matchType, Object... matchValues) {

		if (matchValues.length>matchType.valueMaxLength || matchValues.length<matchType.valueMinLength) {
			throw new IllegalArgumentException(String.format("%s属性选择%s比较类型时，values参数长度非法!", propertyName, matchType));
		}

	}


	/**
	 * 获取比较值的类型.
	 */
	Class<?> getPropertyClass() {
		return propertyClass;
	}

	/**
	 * 获取比较方式.
	 */
	MatchType getMatchType() {
		return matchType;
	}

	/**
	 * 获取比较值.
	 */
	Object[] getMatchValues() {
		return matchValues;
	}

	/**
	 * 获取比较属性名称列表.
	 */
	String getPropertyName() {
		return propertyName;
	}


	/**
	 * 按属性条件参数创建Criterion,辅助函数.
	 */
	@SuppressWarnings({"rawtypes","unchecked"})
	private static <T> Predicate buildCriterion(Root<T> root,HibernateCriteriaBuilder detachedCriteria,final String aliasPropertyName, final MatchType matchType, final Object... propertyValue) {
		Validate.notBlank(aliasPropertyName, "propertyName can't be null");
		// 根据MatchType构造criterion
		Predicate criterion = null;
		//构造属性
		String[] propertyPath = aliasPropertyName.split("\\.");
		SqmPath e = (SqmPath) root.get(propertyPath[0]);
		if(propertyPath.length>1){
			AbstractSqmQualifiedJoin fetch = root.getFetches().stream().filter(f -> f.getAttribute().getName().equals(propertyPath[0]))
					.findFirst()
					.map(tFetch -> (AbstractSqmQualifiedJoin) tFetch)
					.orElseGet(() -> (AbstractSqmQualifiedJoin) root.fetch(propertyPath[0], JoinType.LEFT));
			//构造关联表属性  eg. user.contactInfo.email
			for (int i = 1; i < propertyPath.length; i++) {
				e = fetch.get(propertyPath[i]);
				if(i!=propertyPath.length-1){
					int finalI = i;
					Set<Fetch> fetches = fetch.getFetches();
					AbstractSqmQualifiedJoin finalFetch = fetch;
					fetch = fetches.stream()
							.filter(f->f.getAttribute().getName().equals(propertyPath[finalI]))
							.findFirst()
							.map(tFetch -> (AbstractSqmQualifiedJoin) tFetch)
							.orElseGet(() -> (AbstractSqmQualifiedJoin) finalFetch.fetch(propertyPath[finalI], JoinType.LEFT));
				}
			}
		}
		switch (matchType) {
			case EQ -> criterion = detachedCriteria.equal(e, propertyValue[0]);
			case LIKE -> criterion = detachedCriteria.like(e, "%" + propertyValue[0] + "%");
			case LESS_THAN -> criterion = detachedCriteria.lessThan(e, (Comparable) propertyValue[0]);
			case GREATER_THAN -> criterion = detachedCriteria.greaterThan(e, (Comparable) propertyValue[0]);
			case LESS_THAN_OR_EQ -> criterion = detachedCriteria.lessThanOrEqualTo(e, (Comparable)propertyValue[0]);
			case GREATER_THAN_OR_EQ -> criterion = detachedCriteria.greaterThanOrEqualTo(e,(Comparable)propertyValue[0]);
			case IN -> criterion = e.in(propertyValue);
			case NOT_IN -> criterion = detachedCriteria.not(e.in(propertyValue));
			case NOT_EQ -> criterion = detachedCriteria.notEqual(e, propertyValue[0]);
			case NOT_LIKE -> criterion = detachedCriteria.notLike(e,"%" + propertyValue[0] + "%");
			case START_WITH -> criterion = detachedCriteria.like(e,propertyValue[0] + "%");
			case END_WITH -> criterion = detachedCriteria.like(e,"%"+propertyValue[0]);
			case IS_NULL -> criterion = detachedCriteria.isNull(e);
			case IS_NOT_NULL -> criterion = detachedCriteria.isNotNull(e);
			case BETWEEN -> {
				if (propertyValue.length != 2) {
					throw new BasicCustomException(BasicExceptionCode.INVALID_PARAM);
				}
				criterion = detachedCriteria.between(e.as(e.getJavaType()), (Comparable)propertyValue[0], (Comparable)propertyValue[1]);
			}
			case ARRAY_CONTAINS -> {
				Expression<String> exp = detachedCriteria.function("string_to_array", String.class, detachedCriteria.literal(propertyValue[0]), detachedCriteria.literal(","));
				criterion = detachedCriteria.isTrue(detachedCriteria.function("arraycontains", Boolean.class, e, exp));
			}

		}
		criterion.alias(aliasPropertyName);
		return criterion;
	}


	@Override
	public <T> Predicate buildCriterion(Root<T> root, HibernateCriteriaBuilder detachedCriteria) {

		return buildCriterion(root,detachedCriteria,this.getPropertyName(), this.getMatchType(), this.getMatchValues());
	}


}
